package com.zero.manage.web.components.service.impl;


import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.zero.manage.web.components.base.BaseService;
import com.zero.manage.web.components.dao.SysUserDao;
import com.zero.manage.web.components.dto.LoginDto;
import com.zero.manage.web.components.dto.SysUserDto;
import com.zero.manage.web.components.entity.excel.SysUserExcel;
import com.zero.manage.web.components.entity.rbac.*;
import com.zero.manage.web.components.query.SysOnLineUserQuery;
import com.zero.manage.web.components.query.SysUserQuery;
import com.zero.manage.web.components.service.SysPowerService;
import com.zero.manage.web.components.service.SysUserService;
import com.zero.manage.web.utils.CommonConst;
import io.gitee.zerowsh.config.RedisUtil;
import io.gitee.zerowsh.config.Utils;
import io.gitee.zerowsh.constant.ToolConstant;
import io.gitee.zerowsh.enums.ResultEnums;
import io.gitee.zerowsh.enums.ToolEnums;
import io.gitee.zerowsh.util.*;
import io.swagger.annotations.ApiModel;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;


@Service
@Log4j2
public class SysUserServiceImpl extends BaseService<SysUserEntity, String, SysUserDao, SysUserQuery> implements SysUserService {
    private QSysUserEntity qSysUserEntity = QSysUserEntity.sysUserEntity;
    private QSysUserRoleEntity qSysUserRoleEntity = QSysUserRoleEntity.sysUserRoleEntity;
    private QSysRoleEntity qSysRoleEntity = QSysRoleEntity.sysRoleEntity;
    private QSysRolePowerEntity qSysRolePowerEntity = QSysRolePowerEntity.sysRolePowerEntity;
    private QSysPowerEntity qSysPowerEntity = QSysPowerEntity.sysPowerEntity;

    @Autowired
    private SysPowerService sysPowerService;
    @Value("${rsa-private-key}")
    private String rsaPrivateKey;

    /**
     * 登录逻辑:
     * 1.账号密码解密正确
     * 2.验证码正确
     * 3.账号密码正确
     *
     * @param loginDto
     * @param response
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublicResult login(LoginDto loginDto, HttpServletResponse response) {
        // 获取redis中的验证码
        String verCode = RedisUtil.getStr(String.format(ToolConstant.CAPTCHA_KEY, loginDto.getRedisKey()));
        // 判断验证码
        if (!loginDto.getVerCode().equalsIgnoreCase(verCode)) {
            return PublicResult.resultEnums(ResultEnums.CAPTCHA_FAIL);
        }
        //解密账号密码
        String name = RsaUtil.decrypt(loginDto.getName(), rsaPrivateKey);
        String password = RsaUtil.decrypt(loginDto.getPassword(), rsaPrivateKey);
        if (StrUtil.isBlank(name) || StrUtil.isBlank(password)) {
            return PublicResult.fail("账号或密码无效");
        }
        //登录错误次数
        String loginFailKey = String.format(ToolConstant.LOGIN_FAIL, name);
        String str = RedisUtil.getStr(loginFailKey);
        //计数大于5时，设置用户被锁定一小时
        int i = CommonConst.LOGIN_MAX_FAIL - (StrUtil.isBlank(str) ? 0 : Integer.parseInt(str));
        if (i <= 0) {
            RedisUtil.setExMinutes(loginFailKey, str, CommonConst.LOGIN_LOCK_TIME);
            return PublicResult.fail("账号被冻结");
        }
        RedisUtil.increment(loginFailKey, 1);
        //验证账号
        SysUserDto sysUserDto = this.getUserInfoByName(name);
        if (Objects.isNull(sysUserDto)) {
            return PublicResult.resultEnums(ResultEnums.USER_NOT_EXIST);
        }

        //验证账号状态
        if (Objects.equals(sysUserDto.getState(), ToolEnums.StateEnums.DISABLE)) {
            return PublicResult.resultEnums(ResultEnums.USER_DISABLE);
        }

        //验证密码
        String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
        if (!Objects.equals(sysUserDto.getPassword(), md5Password)) {
            return PublicResult.fail("账号或密码错误", StrUtil.format("账号或密码错误，剩余登录次数 [{}] ", i));
        }

        Map<String, Object> map = this.buildLoginOkInfo(name);
        if (CollectionUtils.isEmpty(map)) {
            return PublicResult.resultEnums(ResultEnums.USER_NOT_POWER);
        }
        String token = JwtUtil.buildTokenInfo(name);
        RedisUtil.setExSeconds(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, name), token, ToolConstant.REDIS_EXPIRE_TIME);
        response.setHeader(ToolConstant.ACCESS_TOKEN_NAME, token);
        response.setHeader("Access-Control-Expose-Headers", ToolConstant.ACCESS_TOKEN_NAME);
        //设置登录时间
        this.updateLoginTime(name);
        RedisUtil.delKey(loginFailKey);
        // 返回菜单
        return PublicResult.resultEnums(ResultEnums.LOGIN_OK, map);
    }

    @Override
    public PublicResult captcha() {
        //定义图形验证码的长和宽
        LineCaptcha specCaptcha = CaptchaUtil.createLineCaptcha(130, 48, 5, 5);
        String verCode = specCaptcha.getCode();
        String redisKey = UUID.randomUUID().toString();
        // 存入redis并设置过期时间
        RedisUtil.setExSeconds(String.format(ToolConstant.CAPTCHA_KEY, redisKey), verCode, ToolConstant.CAPTCHA_EXPIRE_TIME);
        // 将key和base64返回给前端
        Map<String, String> map = new HashMap<>();
        map.put("redisKey", redisKey);
        map.put("image", specCaptcha.getImageBase64Data());
        return PublicResult.queryOk(map);
    }

    /**
     * 构建登录成功返回信息
     * 没有查询到数据禁止登陆并提示
     *
     * @return
     */
    public Map<String, Object> buildLoginOkInfo(String name) {
        Map<String, Object> userMenuAndRouter = sysPowerService.getUserMenuAndRouter(name);
        //必须有菜单和路由
        if (CollectionUtils.isEmpty(userMenuAndRouter)) {
            return null;
        }
        Map<String, Object> map = new HashMap<>(4);
        // 用户信息
        map.put("userInfo", this.getNickNameAndIdByName(name));
        // 菜单信息
        // 路由信息
        map.putAll(userMenuAndRouter);
        // 权限信息
        map.put("powerInfo", this.getPowerList(name));
        return map;
    }

    @Override
    public PublicResult logout() {
        String token = JwtUtil.getToken();
        if (StrUtil.isNotBlank(token)) {
            handleSigOut(token);
            return PublicResult.resultEnums(ResultEnums.LOGOUT_OK);
        } else {
            return PublicResult.resultEnums(ResultEnums.LOGOUT_FAIL);
        }
    }

    public void handleSigOut(String token) {
        String name = JwtUtil.getClaim(token, ToolConstant.MAME);
        if (StrUtil.isNotBlank(name)) {
            String str = RedisUtil.getStr(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, name));
            if (Objects.equals(str, token)) {
                Subject subject = SecurityUtils.getSubject();
                //shiro退出
                subject.logout();
                //删除刷新token和登录token
                RedisUtil.delKey(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, name));
                RedisUtil.delKey(String.format(ToolConstant.REFRESH_ACCESS_TOKEN_PREFIX, name));
            }
        }
    }


    @Override
    public SysUserDto getUserInfoByName(String name) {
        //处理超级用户
        return jpa.select(Objects.equals(name, CommonConst.SUPER_USER)
                ? Projections.bean(SysUserDto.class, qSysUserEntity.password)
                : Projections.bean(SysUserDto.class, qSysUserEntity.password, qSysUserEntity.state))
                .from(qSysUserEntity)
                .where(qSysUserEntity.name.eq(name)).fetchOne();
    }

    @Override
    public String getPasswordByName(String name) {
        return jpa.select(qSysUserEntity.password).from(qSysUserEntity)
                .where(qSysUserEntity.name.eq(name)).fetchOne();
    }

    @Override
    public Map<String, String> getNickNameAndIdByName(String name) {
        Tuple tuple = jpa.select(qSysUserEntity.id, qSysUserEntity.nickName, qSysUserEntity.name).from(qSysUserEntity)
                .where(qSysUserEntity.name.eq(name)).fetchOne();
        Map<String, String> map = new HashMap<>(2);
        if (!Objects.isNull(tuple)) {
            String nickName = tuple.get(qSysUserEntity.nickName);
            map.put("id", tuple.get(qSysUserEntity.id));
            map.put("name", StrUtil.isNotBlank(nickName) ? nickName : tuple.get(qSysUserEntity.name));
        }
        return map;
    }

    @Override
    public List<String> getPowerList(String name) {
        //处理超级用户
        if (Objects.equals(CommonConst.SUPER_USER, name)) {
            return jpa.select(qSysPowerEntity.url).from(qSysPowerEntity)
                    .where(qSysPowerEntity.type.eq(ToolEnums.PowerTypeEnums.POWER)).fetch();
        }
        return jpa.select(qSysPowerEntity.url).from(qSysUserEntity)
                .leftJoin(qSysUserRoleEntity).on(qSysUserEntity.id.eq(qSysUserRoleEntity.userId))
                .leftJoin(qSysRoleEntity).on(qSysUserRoleEntity.roleId.eq(qSysRoleEntity.id))
                .leftJoin(qSysRolePowerEntity).on(qSysRoleEntity.id.eq(qSysRolePowerEntity.roleId))
                .leftJoin(qSysPowerEntity).on(qSysRolePowerEntity.powerId.eq(qSysPowerEntity.id))
                .where(
                        qSysUserEntity.name.eq(name)
                                .and(qSysUserEntity.state.eq(ToolEnums.StateEnums.ENABLE))
                                .and(qSysRoleEntity.state.eq(ToolEnums.StateEnums.ENABLE))
                                .and(qSysPowerEntity.type.eq(ToolEnums.PowerTypeEnums.POWER))
                )
                .fetch();
    }

    @Override
    public PublicResult initFrontEndInfo() {
        Map<String, Object> map = new HashMap<>(3);
        String token = JwtUtil.getToken();
        if (StrUtil.isNotBlank(token)) {
            String name = JwtUtil.getClaim(token, ToolConstant.MAME);
            // 没获取到用户名直接返回空，配合前端实现动态路由
            if (StrUtil.isNotBlank(name)) {
                Map<String, Object> userMenuAndRouter = sysPowerService.getUserMenuAndRouter(name);
                //必须有菜单和路由
                if (!CollectionUtils.isEmpty(userMenuAndRouter)) {
                    map.putAll(userMenuAndRouter);
                    map.put("powerInfo", this.getPowerList(name));
                    return PublicResult.queryOk(map);
                }
            }
        }
        return PublicResult.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublicResult updateState(String id, ToolEnums.StateEnums state) {
        Optional<SysUserEntity> byId = mapper.findById(id);
        if (!byId.isPresent()) {
            return PublicResult.updateOk();
        }
        //处理超级用户
        if (Objects.equals(CommonConst.SUPER_USER, byId.get().getName())) {
            return PublicResult.updateFail("恶意操作");
        }
        jpa.update(qSysUserEntity)
                .set(qSysUserEntity.state, state)
                .set(qSysUserEntity.updateTime, Utils.getCurrentTime())
                .where(qSysUserEntity.id.eq(id))
                .execute();
        return PublicResult.updateOk();
    }

    @Override
    public void updateLoginTime(String name) {
        long currentTime = Utils.getCurrentTime();
        jpa.update(qSysUserEntity)
                .set(qSysUserEntity.loginTime, currentTime)
                .set(qSysUserEntity.updateTime, currentTime)
                .where(qSysUserEntity.name.eq(name))
                .execute();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublicResult resetPassword(String userId, String newPassword) {
        String decrypt = RsaUtil.decrypt(newPassword, rsaPrivateKey);
        if (StrUtil.isBlank(decrypt)) {
            return PublicResult.fail("重置失败", "密码无效");
        }
        newPassword = DigestUtils.md5DigestAsHex(decrypt.getBytes());
        String name = jpa.select(qSysUserEntity.name).from(qSysUserEntity).where(qSysUserEntity.id.eq(userId)).fetchOne();
        if (Objects.equals(name, CommonConst.SUPER_USER)) {
            return PublicResult.fail("重置失败", "恶意操作");
        }
        jpa.update(qSysUserEntity)
                .set(qSysUserEntity.password, newPassword)
                .where(qSysUserEntity.id.eq(userId))
                .execute();
        if (StrUtil.isNotBlank(name)) {
            //删除刷新token和登录token
            RedisUtil.delKey(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, name));
            RedisUtil.delKey(String.format(ToolConstant.REFRESH_ACCESS_TOKEN_PREFIX, name));
        }
        return PublicResult.updateOk();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PublicResult updatePassword(String oldPassword, String newPassword) {
        String oldDecrypt = RsaUtil.decrypt(oldPassword, rsaPrivateKey);
        String newDecrypt = RsaUtil.decrypt(newPassword, rsaPrivateKey);
        if (StrUtil.isBlank(oldDecrypt) || StrUtil.isBlank(newDecrypt)) {
            return PublicResult.builder().code(ResultEnums.UPDATE_FAIL.getKey()).msg("密码无效").build();
        }
        oldPassword = DigestUtils.md5DigestAsHex(oldDecrypt.getBytes());
        newPassword = DigestUtils.md5DigestAsHex(newDecrypt.getBytes());
        String name = JwtUtil.getClaim(JwtUtil.getToken(), ToolConstant.MAME);
        if (!Objects.equals(oldPassword, this.getPasswordByName(name))) {
            return PublicResult.builder().code(ResultEnums.UPDATE_FAIL.getKey()).msg("老密码错误").build();
        }
        jpa.update(qSysUserEntity)
                .set(qSysUserEntity.password, newPassword)
                .where(qSysUserEntity.name.eq(name))
                .execute();
        return PublicResult.updateOk();
    }

    @Override
    public PageResult<List<SysUserEntity>> onlineUserPage(SysOnLineUserQuery query) {
        Set<String> keys = RedisUtil.keys(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, "*"));
        // 获取所有用户账号
        Set<String> collect = keys.stream().map(it -> it.replaceAll(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, ""), "")).collect(Collectors.toSet());
        PageResult.PageResultBuilder<List<SysUserEntity>> pageResultBuilder = PageResult.<List<SysUserEntity>>builder()
                .code(ResultEnums.QUERY_OK.getKey())
                .msg(ResultEnums.QUERY_OK.getMsg())
                .page(query.getPage())
                .pageSize(query.getPageSize());
        if (CollectionUtils.isEmpty(collect)) {
            return pageResultBuilder.build();
        }
        Predicate predicate = query.predicate();
        predicate = ExpressionUtils.and(predicate, qSysUserEntity.name.in(collect));
        Page<SysUserEntity> all = mapper.findAll(predicate, query.getPageable());
        return pageResultBuilder
                .total(all.getTotalElements())
                .data(all.getContent()).build();
    }

    @Override
    public PublicResult forcedOffline(String name) {
        //删除刷新token和登录token
        RedisUtil.delKey(String.format(ToolConstant.ACCESS_TOKEN_PREFIX, name));
        RedisUtil.delKey(String.format(ToolConstant.REFRESH_ACCESS_TOKEN_PREFIX, name));
        return PublicResult.ok();
    }

    @Override
    public PublicResult importInfo(MultipartFile file) throws IOException {
        List<SysUserEntity> list = EasyExcel.read(file.getInputStream()).head(SysUserExcel.class).sheet().doReadSync();
        return PublicResult.resultEnums(ResultEnums.IMPORT_OK, list);
    }

    @Override
    public void exportInfo(HttpServletResponse response, SysUserQuery query) throws IOException {
        //获取类上注解
        ApiModel annotation = SysUserEntity.class.getAnnotation(ApiModel.class);
        List<SysUserExcel> list = jpa.select(
                Projections.bean(SysUserExcel.class, qSysUserEntity.id, qSysUserEntity.name, qSysUserEntity.password)
        ).from(qSysUserEntity).where(query.predicate()).fetch();
        EasyExcelUtil.easyExcelExport(response, SysUserExcel.class, list, annotation.value());
    }


    @Override
    public PublicResult add(SysUserEntity sysUserEntity) {
        String decrypt = RsaUtil.decrypt(sysUserEntity.getPassword(), rsaPrivateKey);
        if (StrUtil.isBlank(decrypt)) {
            return PublicResult.addFail("密码无效");
        }
        sysUserEntity.setPassword(DigestUtils.md5DigestAsHex(decrypt.getBytes()));
        return super.add(sysUserEntity);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public PublicResult deleteByIds(List<SysUserEntity> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return PublicResult.delFail("参数不能为空");
        }
        for (SysUserEntity entity : ids) {
            Optional<SysUserEntity> byId = mapper.findById(entity.getId());
            if (byId.isPresent()) {
                //处理超级用户
                if (Objects.equals(byId.get().getName(), CommonConst.SUPER_USER)) {
                    continue;
                }
                jpa.delete(qSysUserEntity).where(qSysUserEntity.id.eq(byId.get().getId())).execute();
            }
        }
        return PublicResult.delOk();
    }

    @Override
    public PublicResult update(SysUserEntity sysUserEntity) {
        Optional<SysUserEntity> byId = mapper.findById(sysUserEntity.getKeyId());
        if (!byId.isPresent()) {
            return PublicResult.updateOk();
        }
        //处理超级用户
        if (Objects.equals(CommonConst.SUPER_USER, byId.get().getName())) {
            return PublicResult.updateFail("恶意操作");
        }
        sysUserEntity.setUpdateTime(Utils.getCurrentTime());
        sysUserEntity.setCreateTime(byId.get().getCreateTime());
        sysUserEntity.setPassword(byId.get().getPassword());
        mapper.save(sysUserEntity);
        return PublicResult.updateOk();
    }
}
